home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 5
/
Skunkware 5.iso
/
src
/
X11
/
xconq
/
xconq.c
< prev
Wrap
C/C++ Source or Header
|
1995-05-09
|
19KB
|
713 lines
/* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
/* This program may be used, copied, modified, and redistributed freely */
/* for noncommercial purposes, so long as this notice remains intact. */
#pragma comment(exestr, "@(#) xconq.c 12.1 95/05/09 ")
/* RCS $Header: xconq.c,v 1.4 88/07/17 17:11:08 shebs Exp $ */
/* Unlike most multi-player network programs, xconq does not use multiple */
/* processes. Instead, it relies on the network capability inherent in */
/* some graphical interfaces (such as X) to open up multiple displays. */
#include "config.h"
#include "misc.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"
#include "global.h"
#include "version.h"
/* This is for extremely simple menus to be used with novice players. */
typedef struct menu_entry {
char *name;
char *description;
} MenuEntry;
extern int nummaps; /* needed to initialize number of maps read */
/* Definitions of globally global variables. */
Global global; /* important (i.e. saved/restored) globals */
MenuEntry mapmenu[MAXMAPMENU]; /* list of mapfiles to suggest to players */
bool givenseen = FALSE; /* true if world known */
bool Debug = FALSE; /* the debugging flag itself. */
bool Build = FALSE; /* magic flag for scenario-builders */
bool Freeze = FALSE; /* keeps machine players from moving */
bool humans[MAXSIDES]; /* flags for which players are human or machine */
char *programname; /* name of the main program */
char *rawfilenames[MAXLOADED]; /* names specified on cmd line */
char version[] = VERSION; /* version string */
char *hosts[MAXSIDES]; /* names of displays for each side */
char spbuf[BUFSIZE]; /* main printing buffer */
char tmpbuf[BUFSIZE]; /* a temp buffer */
int numfiles = 0; /* number of data files asked for */
int numgivens = 0; /* number of sides given on cmd line */
int numhumans = 0; /* number of bona-fide human players */
int givenwidth = DEFAULTWIDTH; /* requested width for a random map */
int givenheight = DEFAULTHEIGHT; /* requested height for a random map */
int giventime = 0; /* requested time for individual turns */
/* Indices of groups of mapfile types mentioned in collection of mapfiles. */
int scnstart, scnend, perstart, perend, mapstart, mapend;
Side *winner = NULL; /* the winning side (allies are also winners) */
/* Where it all begins... the main program's primary duty is command line */
/* interpretation, it hands off for everything else. */
main(argc, argv)
int argc;
char *argv[];
{
bool eopt = FALSE, ask = FALSE;
char ch;
int i, numenemies;
programname = argv[0];
nummaps = 0;
for (i = 0; i < MAXLOADED; ++i) rawfilenames[i] = "";
add_default_player();
for (i = 1; i < argc; ++i) {
if ((argv[i])[0] == '-') {
ch = (argv[i])[1];
if (Debug) printf("-%c\n", ch);
switch (ch) {
case 'A':
if (i+1 >= argc) usage_error();
add_player(FALSE, argv[++i]);
break;
case 'B':
Build = TRUE;
Freeze = TRUE;
break;
case 'D':
Debug = TRUE;
break;
case 'e':
if (i+1 >= argc) usage_error();
eopt = TRUE;
numenemies = atoi(argv[++i]);
while (numenemies-- > 0) add_player(FALSE, NULL);
break;
case 'm':
if (i+1 >= argc) usage_error();
make_pathname(NULL, argv[++i], "map", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
break;
case 'M':
if (i+2 >= argc) usage_error();
givenwidth = atoi(argv[++i]);
givenheight = atoi(argv[++i]);
break;
case 'p':
if (i+1 >= argc) usage_error();
make_pathname(NULL, argv[++i], "per", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
break;
case 'r':
if (numgivens > 1) {
fprintf(stderr, "Warning: -r is resetting the list of\n");
fprintf(stderr, "players already given in the command.\n");
}
numgivens = numhumans = 0;
break;
case 's':
if (i+1 >= argc) usage_error();
make_pathname(NULL, argv[++i], "scn", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
break;
case 't':
if (i+1 >= argc) usage_error();
/* Converting to seconds for internal use */
giventime = 60 * atoi(argv[++i]);
break;
case 'v':
givenseen = TRUE;
break;
case 'x':
ask = TRUE;
break;
default:
usage_error();
}
} else {
/* We just have a host name for a human player */
add_player(TRUE, argv[i]);
}
}
/* Put in a single machine opponent if no -e and no human opponents */
if (!eopt && numgivens == 1) add_player(FALSE, NULL);
/* Say hi to the user */
printf("\n Welcome to XCONQ version %s\n\n", version);
maybe_dump_news();
/* If no command-line options supplied, work interactively */
if (ask) {
if (read_menu_file()) {
ask_about_mapfiles();
} else {
printf("No period/map/scenario menus available.\n");
}
ask_about_players();
}
/* While away the hours setting everything up */
init_random(-1);
init_sides();
init_units();
init_game();
init_movement();
init_displays();
init_mplayers();
init_sighandlers();
/* Relatively low chance of screwup now, so safe to delete saved game */
if (saved_game()) remove_saved_game();
/* Play! */
play();
}
/* Complain and leave if command is garbage. */
usage_error()
{
fprintf(stderr, "Usage: %s [display] [-A display] [-B] [-e n] [-m name]\n",
programname);
fprintf(stderr, " [-M w h] [-p name] [-r] [-s name] [-t n] [-v] [-x]\n");
exit(1);
}
/* Add a player into the array of players, making sure of no overflow. */
/* Fail if so, players probably made a typo on command line - help them */
/* out with a list of what players had been included. */
add_player(ahuman, host)
bool ahuman;
char *host;
{
if (numgivens >= MAXSIDES) {
fprintf(stderr, "At most %d player sides allowed!\n", MAXSIDES);
fprintf(stderr, "(Valid ones were ");
list_players(stderr);
fprintf(stderr, ")\n");
exit(1);
}
hosts[numgivens] = host;
humans[numgivens] = ahuman;
numgivens++;
if (ahuman) numhumans++;
if (Debug) printf("Added %s player (%s)\n",
(ahuman ? "human" : "machine"), (host ? host : ""));
}
/* The main loop that cycles through the turns and the phases. */
/* Always departs through game end phase (or by core dump :-( ). */
play()
{
while (TRUE) {
init_turn_phase();
spy_phase();
disaster_phase();
build_phase();
flush_dead_units();
supply_phase();
sort_units(FALSE);
movement_phase();
consumption_phase();
flush_dead_units();
game_end_phase();
}
}
/* Do random small things to get the turn started, including resetting */
/* some side vars that could get things confused, if set wrong. */
/* Most movement-related things should get set later, at beginning of */
/* move phase. */
init_turn_phase()
{
Side *side;
++global.time;
if (Debug) printf("##### TURN %d #####\n", global.time);
for_all_sides(side) {
wedge_mode(side);
show_timemode(side);
if (giventime > 0) update_clock(side);
flush_input(side);
flush_output(side);
}
}
/* The win/lose phase assesses the sides' performance during that turn, */
/* and can end the game. In fact, it is the only way to get out, except */
/* for the quick exit command. With multiple mixed players, there are a */
/* number of possibilities at game end, such as an all machine win. Also, */
/* it is possible for all players to lose simultaneously! */
game_end_phase()
{
bool enemiesleft = FALSE;
int sidesleft = 0, testrslt;
Side *side, *side2, *survivor;
if (Debug) printf("Entering game end phase\n");
/* See if any sides have won or lost */
if (global.time >= global.endtime) {
notify_all("The end of the world has arrived!");
for_all_sides(side) side_loses(side, NULL);
} else if (Build) {
/* No winning or losing while building */
} else {
for_all_sides(side) {
if (!side->lost) {
if (side->timedout) {
notify_all("The %s ran out of time!",
plural_form(side->name));
side_loses(side, NULL);
} else if (all_units_gone(side)) {
notify_all("The %s have been wiped out!",
plural_form(side->name));
side_loses(side, NULL);
} else {
testrslt = condition_true(side);
if (testrslt > 0) side_wins(side);
if (testrslt < 0) side_loses(side, NULL);
}
}
}
}
/* See if any opposing sides left */
for_all_sides(side) {
for_all_sides(side2) {
if (!side->lost && !side2->lost && enemy_side(side, side2))
enemiesleft = TRUE;
}
if (!side->lost) {
survivor = side;
sidesleft++;
}
}
/* Decide if the game is over */
if (numsides == 1) {
/* A one-player game can't end */
} else if (sidesleft == 0) {
exit_xconq();
} else if (!enemiesleft) {
winner = survivor;
if (sidesleft == 1) {
notify(winner, "You have prevailed over your enemies!");
} else {
for_all_sides(side) {
if (!side->lost) {
notify(side, "Your alliance has defeated its enemies!");
}
}
}
wait_to_exit(winner);
exit_xconq();
} else {
/* Game isn't over yet */
}
}
/* Quicky test for absence of all units. */
all_units_gone(side)
Side *side;
{
int u;
for_all_unit_types(u) if (side->units[u] > 0) return FALSE;
return TRUE;
}
/* Check out all the winning and losing conditions, returning a flag */
/* on which, if any, are true. The three return values are 1 for win, */
/* -1 for lose, and 0 for undecided. */
condition_true(side)
Side *side;
{
int i, u, r, amt = 0;
Unit *unit;
Condition *cond;
for (i = 0; i < global.numconds; ++i) {
cond = &(global.winlose[i]);
if ((between(cond->starttime, global.time, cond->endtime)) &&
(cond->sidesn == -1 || cond->sidesn == side_number(side))) {
switch (cond->type) {
case TERRITORY:
for_all_unit_types(u) {
amt += side->units[u] * utypes[u].territory;
}
if (cond->win) {
if (amt >= cond->n) {
notify_all("The %s have enough territory to win!",
plural_form(side->name));
return 1;
}
} else {
if (amt < cond->n) {
notify_all("The %s don't have enough territory!",
plural_form(side->name));
return -1;
}
}
break;
case UNITCOUNT:
if (cond->win) {
for_all_unit_types(u) {
if (side->units[u] < cond->units[u]) return 0;
}
notify_all("The %s have enough units to win!",
plural_form(side->name));
return 1;
} else {
for_all_unit_types(u) {
if (side->units[u] > cond->units[u]) return 0;
}
notify_all("The %s don't have the required units!",
plural_form(side->name));
return -1;
}
break;
case RESOURCECOUNT:
if (cond->win) {
for_all_resource_types(r) {
if (side->resources[r] < cond->resources[r]) return 0;
}
notify_all("The %s have enough resources to win!",
plural_form(side->name));
return 1;
} else {
for_all_resource_types(r) {
if (side->resources[r] > cond->resources[r]) return 0;
}
notify_all("The %s don't have the required resources!",
plural_form(side->name));
return -1;
}
break;
case POSSESSION:
unit = unit_at(cond->x, cond->y);
if (cond->win) {
if (unit != NULL && unit->side == side) {
notify_all("The %s have got hex %d,%d!",
plural_form(side->name), cond->x, cond->y);
return 1;
}
} else {
if (unit == NULL || unit->side != side) {
notify_all("The %s don't have hex %d,%d!",
plural_form(side->name), cond->x, cond->y);
return -1;
}
}
break;
default:
case_panic("condition type", cond->type);
break;
}
}
}
return 0;
}
/* Winning is defined as not losing :-) */
side_wins(side)
Side *side;
{
Side *side2;
for_all_sides(side2) {
if (!allied_side(side, side2)) side_loses(side2, NULL);
}
}
/* Be rude to losers and give all their units to their defeaters (might */
/* not be one, so units become neutral or dead). Give the loser a chance */
/* to study the screen, mark the side as lost, then pass on this detail to */
/* all the other sides and remove spurious leftover images of units. */
side_loses(side, victor)
Side *side, *victor;
{
int ux, uy;
Unit *unit;
Side *side2;
notify(side, "You lost, %s!", (flip_coin() ? "sucker" : "turkey"));
if (active_display(side)) {
wait_to_exit(side);
close_display(side);
wedge_mode(side);
}
for_all_units(unit) {
if (alive(unit) && unit->side == side) {
ux = unit->x; uy = unit->y;
unit_changes_side(unit, victor, CAPTURE, SURRENDER);
all_see_hex(ux, uy);
}
}
side->lost = TRUE;
for_all_sides(side2) {
if (active_display(side2)) {
remove_images(side2, side_number(side));
if (side != side2) show_all_sides(side2);
}
}
}
/* Leave screen up so everybody can study it, and allow any input to take */
/* it down. OK to freeze all the other sides at once here. */
wait_to_exit(side)
Side *side;
{
if (humanside(side) && active_display(side)) {
sprintf(side->promptbuf, "[Do anything to exit]");
show_prompt(side);
freeze_wait(side);
}
}
/* This routine should be called before any sort of non-death exit. */
/* Among other things, it updates the statistics. */
exit_xconq()
{
int n = 0;
Unit *unit;
Side *side;
close_displays();
/* Need to kill off all units to finish up statistics */
for_all_units(unit) kill_unit(unit, (winner ? VICTOR : SURRENDER));
print_statistics();
/* Offer a one-line comment on the game and then leave */
for_all_sides(side) if (!side->lost) n++;
switch (n) {
case 0:
printf("\nEverybody lost!\n\n");
break;
case 1:
printf("\nThe %s (%s) won!\n\n",
plural_form(winner->name),
(winner->host ? winner->host : "machine"));
break;
default:
sprintf(spbuf, "");
for_all_sides(side) {
if (!side->lost) {
if (strlen(spbuf) > 0) {
sprintf(tmpbuf, "/");
strcat(spbuf, tmpbuf);
}
sprintf(tmpbuf, "%s", side->name);
strcat(spbuf, tmpbuf);
if (side->host) {
sprintf(tmpbuf, "(%s)", side->host);
strcat(spbuf, tmpbuf);
}
}
}
printf("\nThe %s alliance won!\n\n", spbuf);
break;
}
exit(0);
}
/* Shut down displays - should be done before any sort of exit. */
close_displays()
{
Side *side;
for_all_sides(side) if (active_display(side)) close_display(side);
}
/* This is to find out how everybody did. */
print_statistics()
{
Side *side;
FILE *fp;
#ifdef STATISTICS
if ((fp = fopen(STATSFILE, "w")) != NULL) {
for_all_sides(side) {
print_side_results(fp, side);
print_unit_record(fp, side);
print_combat_results(fp, side);
if (side->next != NULL) fprintf(fp, "\f\n");
}
fclose(fp);
} else {
fprintf(stderr, "Can't open statistics file \"%s\"\n", STATSFILE);
}
#endif /* STATISTICS */
}
/* Read in a file in a special format - basically lines describing mapfiles */
/* with markers for various types in between. Returns true if the file */
/* was actually found. */
read_menu_file()
{
int i, j;
char *tmp;
FILE *fp;
scnstart = scnend = perstart = perend = mapstart = mapend = 0;
make_pathname(XCONQLIB, MAPFILEFILE, NULL, spbuf);
if ((fp = fopen(spbuf, "r")) != NULL) {
for (i = 0; i < MAXMAPMENU; ++i) {
fscanf(fp, "%s", tmpbuf);
mapmenu[i].name = copy_string(tmpbuf);
tmp = read_line(fp);
for (j = 0; tmp[j] != '\0'; ++j) if (tmp[j] != ' ') break;
mapmenu[i].description = tmp + j;
if (strcmp(mapmenu[i].name, "Scenarios") == 0) {
scnstart = i+1;
} else if (strcmp(mapmenu[i].name, "Periods") == 0) {
scnend = i-1;
perstart = i+1;
} else if (strcmp(mapmenu[i].name, "Maps") == 0) {
perend = i-1;
mapstart = i+1;
} else if (strcmp(mapmenu[i].name, "End") == 0) {
mapend = i-1;
break;
}
}
fclose(fp);
return TRUE;
} else {
return FALSE;
}
}
/* Display menus of mapfiles. Player can either choose a single scenario */
/* or a period + map (but no guarantees that map will work with period!) */
ask_about_mapfiles()
{
char *name;
if (file_menu(scnstart, scnend, "scenario",
"Enter a number, or hit return to look at maps and periods.",
&name)) {
make_pathname(NULL, name, "scn", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
} else {
if (file_menu(perstart, perend, "historical period",
"Enter a number, or hit return for the standard period.",
&name)) {
make_pathname(NULL, name, "per", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
} else {
printf("\nYou will get the standard period.\n");
}
if (file_menu(mapstart, mapend, "map",
"Enter a number, or hit return for a random map.",
&name)) {
make_pathname(NULL, name, "map", spbuf);
rawfilenames[numfiles++] = copy_string(spbuf);
} else {
printf("\nYou will get a random map.\n");
}
}
}
/* Given an array of names, return success or failure of choice and maybe */
/* one of the names. */
file_menu(start, end, prompt, help, rslt)
int start, end;
char *prompt, *help, **rslt;
{
int i, ans;
printf("\n");
if (start > end) return FALSE;
for (i = start; i <= end; ++i) {
printf("[%d] %-14s %s\n",
i - start + 1, mapmenu[i].name, mapmenu[i].description);
}
while (TRUE) {
printf("\nChoose a %s: ", prompt);
gets(spbuf);
sscanf(spbuf, "%d", &ans);
ans += start - 1;
if (ans >= start && ans <= end) {
*rslt = mapmenu[ans].name;
return TRUE;
} else if (iindex('?', spbuf) >= 0) {
printf("%s\n", help);
} else if (strlen(spbuf) == 0) {
return FALSE;
} else {
printf("Garbled answer! Try again...\n");
}
}
}
/* This allows fairly general setup for a collection of players. There */
/* should be facilities for removing as well as adding... */
ask_about_players()
{
int i, n;
while (TRUE) {
printf("\nPlayers so far: ");
list_players(stdout);
printf("\n\nAny others? [number or hostname]: ");
gets(spbuf);
if (strlen(spbuf) == 0) {
return;
} else if (strcmp(spbuf, "r") == 0) {
numgivens = 0;
} else if (iindex('?', spbuf) >= 0) {
printf("You can make various combinations of players,\n");
printf("by giving either a number of machine or a host name.\n");
printf("`r' clears the list; hit return when done.\n");
} else if (numgivens < MAXSIDES) {
if (sscanf(spbuf, "%d", &n) == 1) {
n = max(0, min(n, MAXSIDES - numgivens));
for (i = 0; i < n; ++i) add_player(FALSE, NULL);
} else {
add_player(TRUE, copy_string(spbuf));
}
} else {
printf("Can't add anybody else; 'r' starts over.\n");
}
}
}
/* List all the specified players briefly. */
list_players(fp)
FILE *fp;
{
int i;
if (numgivens > 0) {
fprintf(fp, "%s", (hosts[0] ? hosts[0] : "machine"));
for (i = 1; i < numgivens; ++i) {
fprintf(fp, ", %s", (hosts[i] ? hosts[i] : "machine"));
}
} else {
fprintf(fp, "no players defined.");
}
}